import {
  EXTERNAL_LINK_DOMAINS,
  getCurrentOrigin,
  getCurrentSearch,
  logger,
  VALID_RESULT,
} from '../../../types/constants'

/**
 * URL参数构建配置选项
 */
interface UrlParamOptions {
  /** 是否跳过默认值（如 page=1 时是否省略） */
  skipDefaultValue?: boolean
  /** 参数的默认值（用于判断是否跳过） */
  defaultValue?: number | string
  /** 参数编码方式 */
  encode?: boolean
  /** 是否保留原始 URL 的哈希部分 */
  preserveHash?: boolean
  /** 是否保留原始 URL 的现有参数 */
  preserveExistingParams?: boolean
  /** 参数值验证函数 */
  validator?: (value: any) => boolean
}

/**
 * URL 组成部分
 */
interface UrlParts {
  protocol: string
  host: string
  pathname: string
  search: string
  hash: string
}

interface UrlPathPrefixOptions {
  includeTrailingSlash?: boolean // 是否包含尾部斜杠
  includeQueryParams?: boolean // 是否包含查询参数
  includeHash?: boolean // 是否包含 hash
}

interface UrlPathSuffixOptions {
  includeTrailingSlash?: boolean // 是否包含尾部斜杠
  includeQueryParams?: boolean // 是否包含查询参数
  includeHash?: boolean // 是否包含 hash
  startFromIndex?: number // 从指定索引开始（默认为0）
}

interface IntegrityResult {
  value: string
  source: string | null
  param: string | null
  algorithm: string | null
  rawValue: string | null
}

/**
 * URL 工具类
 */
export class UrlUtils {
  get name(): string {
    return 'UrlUtils'
  }

  /**
   * 检查 URL 是否合法
   * @param url 要检查的URL
   * @param type 上传模式
   * @returns 返回检查结果
   */
  static isValidUrl(url: string, type: FileUploadMode): { status: ValidResultType, error: string } {
    // 空值检查
    if (!url || typeof url !== 'string') {
      return {
        status: VALID_RESULT.INVALID.value,
        error: 'URL不能为空且必须是字符串',
      }
    }

    // 去除前后空格
    const trimmedUrl = url.trim()

    // 文件上传模式验证
    if (type === 'file') {
      const isDataUrl = trimmedUrl.startsWith('data:image/')
      const isBlobUrl = trimmedUrl.startsWith('blob:')

      return {
        status: isDataUrl || isBlobUrl ? VALID_RESULT.VALID.value : VALID_RESULT.INVALID.value,
        error: isDataUrl || isBlobUrl
          ? ''
          : '文件URL必须以"data:image/"或"blob:"开头',
      }
    }

    try {
      const urlObj = new URL(url)

      // 协议验证
      if (!['http:', 'https:'].includes(urlObj.protocol)) {
        return {
          status: VALID_RESULT.INVALID.value,
          error: `不支持的协议: ${urlObj.protocol}，只支持http和https`,
        }
      }

      // 域名验证
      if (!urlObj.hostname) {
        return {
          status: VALID_RESULT.INVALID.value,
          error: 'URL必须包含有效域名',
        }
      }

      return {
        status: VALID_RESULT.VALID.value,
        error: '',
      }
    }
    catch (error: any) {
      return { status: VALID_RESULT.INVALID.value, error: `无效的URL格式: ${error.message}` }
    }
  }

  /**
   * 检查链接是否为外部链接
   * @param href 链接地址
   * @param whiteList 白名单
   * @returns 是否为外部链接
   */
  static isExternalLink(href: string, whiteList?: string[]): boolean {
    try {
      const { hostname } = new URL(href)
      const domains = whiteList && whiteList.length > 0 ? whiteList : EXTERNAL_LINK_DOMAINS

      return domains.some((domain: string) => {
        if (domain.startsWith('*.')) {
          return hostname.endsWith(domain.slice(1)) // 移除通配符
        }
        return hostname === domain
      })
    }
    catch {
      return false // 无效 URL 视为内部链接
    }
  }

  /**
   * 获取完整的 URL
   * @param path 路径
   * @param prependOrigin 是否添加 ORIGINAL_ORIGIN（默认为 true）
   * @returns 完整的 URL
   */
  static getFullUrl(path: string | undefined, prependOrigin: boolean = true): string {
    const isFullUrl = path && (path.startsWith('http://') || path.startsWith('https://'))

    if (isFullUrl)
      return path

    const separator = '/'

    // 处理空路径情况
    if (!path)
      return prependOrigin ? getCurrentOrigin() : separator

    // 处理 path 开头是否有斜杠的情况
    const normalizedPath = path.startsWith(separator) ? path : `${separator}${path}`

    return prependOrigin ? `${getCurrentOrigin()}${normalizedPath}` : normalizedPath.replace(separator, '')
  }

  /**
   * 解析 URL 搜索参数
   */
  static parseUrlParams(url: string): Record<string, string> {
    try {
      const params = new URL(url).searchParams
      return Object.fromEntries(params.entries())
    }
    catch {
      return {}
    }
  }

  /**
   * 获取 URL 参数
   */
  static getUrlParams(param: string): string {
    return new URLSearchParams(getCurrentSearch()).get(param) || ''
  }

  /**
   * 替换 URL 中的指定参数值
   * @param url 原始 URL
   * @param paramName 参数名
   * @param newValue 新参数值
   * @returns 替换后的 URL
   */
  static replaceUrlParam(url: string, paramName: string, newValue: string | number): string {
    try {
      const urlObj = new URL(url)

      // 设置新的参数值
      urlObj.searchParams.set(paramName, String(newValue))

      return urlObj.toString()
    }
    catch (error: any) {
      logger.warn(`[${this.name}] URL 参数替换失败，返回原URL: ${error.message}`)
      return url
    }
  }

  /**
   * 批量替换 URL 参数
   * @param url 原始 URL
   * @param params 参数对象 {参数名: 新值}
   * @returns 替换后的 URL
   */
  static replaceUrlParams(url: string, params: Record<string, string | number>): string {
    try {
      const urlObj = new URL(url)

      Object.entries(params).forEach(([paramName, newValue]) => {
        urlObj.searchParams.set(paramName, String(newValue))
      })

      return urlObj.toString()
    }
    catch (error: any) {
      logger.warn(`[${this.name}] URL 参数批量替换失败，返回原URL: ${error.message}`)
      return url
    }
  }

  /**
   * 删除 URL 中的指定参数
   * @param url 原始 URL
   * @param paramName 要删除的参数名
   * @returns 删除参数后的 URL
   */
  static removeUrlParam(url: string, paramName: string): string {
    try {
      const urlObj = new URL(url)
      urlObj.searchParams.delete(paramName)
      return urlObj.toString()
    }
    catch (error: any) {
      logger.warn(`[${this.name}] URL 参数删除失败，返回原URL: ${error.message}`)
      return url
    }
  }

  /**
   * 获取 URL 中的指定参数值
   * @param url URL 字符串
   * @param paramName 参数名
   * @returns 参数值，如果不存在返回 null
   */
  static getUrlParam(url: string, paramName: string): string | null {
    try {
      return new URL(url).searchParams.get(paramName)
    }
    catch {
      return null
    }
  }

  /**
   * 检查 URL 是否包含指定参数
   * @param url URL 字符串
   * @param paramName 参数名
   * @returns 是否包含该参数
   */
  static hasUrlParam(url: string, paramName: string): boolean {
    try {
      return new URL(url).searchParams.has(paramName)
    }
    catch {
      return false
    }
  }

  /**
   * 检查是否是有效可导航 href
   */
  static isValidHref(href: string | undefined): boolean {
    return !!href
      && !href.startsWith('javascript:')
      && !href.startsWith('#')
      && href !== 'javascript:void(0)'
      && href !== ''
  }

  /**
   * 在当前标签页打开链接
   */
  static openTab(url: string): void {
    window.location.href = url
  }

  /**
   * 打开新标签页
   * @param url url 地址
   * @param type 打开方式
   */
  static openNewTab(url: string, type: 'GM' | 'Browser' = 'GM'): void {
    type === 'GM'
      ? GM_openInTab(url, { active: true, setParent: true })
      : window.open(url, '_blank', 'noopener,noreferrer')
  }

  /**
   * 构建带参数的 URL
   */
  static buildUrlParam(baseUrl: string, paramName: string, paramValue: number | string, options: UrlParamOptions = {}): string {
    const {
      skipDefaultValue = true,
      defaultValue = 1,
      encode = true,
      preserveHash = true,
      preserveExistingParams = true,
      validator,
    } = options

    // 参数验证
    if (validator && !validator(paramValue))
      throw new Error(`参数值验证失败: ${paramName}=${paramValue}`)

    // 检查是否跳过默认值
    if (skipDefaultValue && paramValue === defaultValue)
      return baseUrl

    // 解析 URL
    const urlParts = this.parseUrl(baseUrl, preserveHash)
    const params = preserveExistingParams
      ? new URLSearchParams(urlParts.search)
      : new URLSearchParams()

    // 设置参数值
    const encodedValue = encode ? encodeURIComponent(String(paramValue)) : String(paramValue)
    params.set(paramName, encodedValue)

    // 重新构建 URL
    return this.buildUrl(urlParts, params)
  }

  /**
   * 解析 URL 为组成部分
   */
  private static parseUrl(url: string, preserveHash: boolean): UrlParts {
    try {
      const urlObj = new URL(url, getCurrentOrigin())
      return {
        protocol: urlObj.protocol,
        host: urlObj.host,
        pathname: urlObj.pathname,
        search: urlObj.search,
        hash: preserveHash ? urlObj.hash : '',
      }
    }
    catch {
      // 如果是相对URL，手动解析
      const [pathPart, hashPart] = url.split('#')
      const [basePart, searchPart] = pathPart.split('?')

      return {
        protocol: '',
        host: '',
        pathname: basePart,
        search: searchPart ? `?${searchPart}` : '',
        hash: preserveHash && hashPart ? `#${hashPart}` : '',
      }
    }
  }

  /**
   * 从组成部分构建URL
   */
  private static buildUrl(parts: UrlParts, params: URLSearchParams): string {
    const search = params.toString() ? `?${params.toString()}` : ''

    let url = parts.pathname + search + parts.hash

    if (parts.protocol && parts.host) {
      url = `${parts.protocol}//${parts.host}${url}`
    }

    return url
  }

  /**
   * 缩短 URL 显示
   */
  static shortenUrl(url: string): string {
    if (url.length <= 50)
      return url

    return `${url.substring(0, 25)}...${url.substring(url.length - 20)}`
  }

  /**
   * 获取 URL pathname 前 N 个部分的完整 URL，支持替换第 N 个路径部分
   * @param url 原始 URL
   * @param n 要获取的路径部分数量
   * @param options 配置选项
   * @returns 前 N 个路径部分的完整 URL
   */
  static getUrlPathnamePrefix(url: string, n: number, options: UrlPathPrefixOptions = {}): string {
    try {
      const urlObj = new URL(url)
      const pathParts = urlObj.pathname.split('/').filter(part => part !== '')

      // 获取前 N 个路径部分
      const prefixParts = pathParts.slice(0, n)

      // 构建新的 URL
      urlObj.pathname = `/${prefixParts.join('/')}`

      // 处理尾部斜杠
      if (options.includeTrailingSlash && prefixParts.length > 0) {
        urlObj.pathname += '/'
      }

      // 根据选项处理查询参数和 hash
      if (!options.includeQueryParams) {
        urlObj.search = ''
      }

      if (!options.includeHash) {
        urlObj.hash = ''
      }

      return urlObj.toString()
    }
    catch (error) {
      logger.error('解析 URL 失败:', error)
      return url
    }
  }

  /**
   * 获取 URL pathname 从第 N 个部分开始的后缀内容
   * @param url 原始 URL
   * @param n 从第几个路径部分开始（从0开始）
   * @param options 配置选项
   * @returns 从第 N 个部分开始的后缀路径
   */
  static getUrlPathnameSuffix(url: string, n: number, options: UrlPathSuffixOptions = {}): string {
    try {
      const urlObj = new URL(url)
      const pathParts = urlObj.pathname.split('/').filter(part => part !== '')

      const {
        includeTrailingSlash = false,
        includeQueryParams = true,
        includeHash = true,
        startFromIndex = 0,
      } = options

      // 验证索引范围
      const startIndex = Math.max(0, Math.min(n - 1, pathParts.length))
      const actualStartIndex = Math.max(startFromIndex, startIndex)

      // 获取从第 N 个部分开始的后缀
      const suffixParts = pathParts.slice(actualStartIndex)

      if (suffixParts.length === 0) {
        return ''
      }

      // 构建路径
      let suffixPath = `/${suffixParts.join('/')}`

      // 处理尾部斜杠
      if (includeTrailingSlash) {
        suffixPath += '/'
      }

      // 添加查询参数和 hash（如果需要）
      let result = suffixPath

      if (includeQueryParams && urlObj.search) {
        result += urlObj.search
      }

      if (includeHash && urlObj.hash) {
        result += urlObj.hash
      }

      return result
    }
    catch (error) {
      logger.error('解析 URL 失败:', error)
      return ''
    }
  }

  /**
   * 替换路径数组中的第 N 个部分（带校验）
   * @param input 输入可以是 URL 字符串或路径部分数组
   * @param index 要替换的索引位置
   * @param value 替换值
   * @param options 配置选项
   * @param options.originalPathParts 原始完整路径数组（用于边界检查）
   * @param options.validate 校验函数，返回 true 才进行替换
   * @returns 替换后的路径数组或完整URL（根据输入类型决定）
   */
  static replacePathPart(
    input: string | string[],
    index: number,
    value: string,
    options: {
      originalPathParts?: string[]
      validate?: (currentValue: string, index: number, pathParts: string[]) => boolean
    },
  ): string | string[] {
    const { originalPathParts, validate } = options

    // 处理URL字符串输入
    if (typeof input === 'string') {
      try {
        const urlObj = new URL(input)
        const pathParts = urlObj.pathname.split('/').filter(part => part !== '')

        // 执行替换
        const resultParts = this.replacePathPartInternal(
          pathParts,
          index,
          value,
          {
            originalPathParts,
            validate,
          },
        )

        // 构建新的URL
        urlObj.pathname = `/${resultParts.join('/')}`
        return urlObj.toString()
      }
      catch (error) {
        logger.error('解析 URL 失败:', error)
        return input // 返回原始输入
      }
    }

    // 处理路径数组输入
    return this.replacePathPartInternal(input, index, value, {
      originalPathParts,
      validate,
    })
  }

  /**
   * 内部替换函数（处理路径数组）
   */
  private static replacePathPartInternal(
    pathParts: string[],
    index: number,
    value: string,
    options: {
      originalPathParts?: string[]
      validate?: (currentValue: string, index: number, pathParts: string[]) => boolean
    },
  ): string[] {
    const { originalPathParts, validate } = options
    index = index - 1

    if (!pathParts.length) {
      return pathParts
    }

    // 验证索引范围
    if (index < 0) {
      return pathParts
    }

    // 深拷贝数组避免修改原数组
    const resultParts = [...pathParts]

    // 检查是否在现有范围内
    if (index < resultParts.length) {
      const currentValue = resultParts[index]

      // 执行校验（如果提供了校验函数）
      if (validate && !validate(currentValue, index, resultParts)) {
      // 校验失败，返回原数组
        return pathParts
      }

      // 校验通过或没有校验函数，进行替换
      resultParts[index] = value
    }
    // 检查是否可以在末尾添加（需要原始路径作为参考）
    else if (index === resultParts.length && originalPathParts && index < originalPathParts.length) {
    // 添加新部分
      resultParts.push(value)
    }
    // 索引超出范围，不做任何修改
    else {
      return pathParts
    }

    return resultParts
  }

  /**
   * 从 URL 中提取 integrity 哈希值
   * 支持多种格式：sha256-xxx, sha256=xxx, #sha256=xxx, &sha256=xxx
   */
  static extractIntegrityFromUrl(url: string): IntegrityResult | null {
    try {
      const urlObj = new URL(url)

      // 1. 首先检查 URL 参数中的 integrity
      const paramIntegrity = this.extractFromUrlParams(urlObj)
      if (paramIntegrity)
        return paramIntegrity

      // 2. 检查 hash 片段中的 integrity
      const hashIntegrity = this.extractFromHashFragment(urlObj.hash)
      if (hashIntegrity)
        return hashIntegrity

      // 3. 最后在整个 URL 中搜索
      const fullUrlIntegrity = this.extractFromFullUrl(url)
      if (fullUrlIntegrity)
        return fullUrlIntegrity

      return null
    }
    catch (error) {
      console.warn('URL 解析失败:', error)
      return null
    }
  }

  /**
   * 从 URL 参数中提取 integrity
   */
  private static extractFromUrlParams(urlObj: URL): IntegrityResult | null {
    const integrityParams = [
      'integrity',
      'hash',
      'sha256',
      'sha',
      'digest',
      'checksum',
    ]

    for (const param of integrityParams) {
      const value = urlObj.searchParams.get(param)
      if (value && this.isValidIntegrity(value)) {
        return {
          value,
          source: 'query',
          param,
          algorithm: this.detectAlgorithm(value),
          rawValue: value,
        }
      }
    }

    return null
  }

  /**
   * 从 hash 片段中提取 integrity
   */
  private static extractFromHashFragment(hash: string): IntegrityResult | null {
    if (!hash)
      return null

    // 支持 #sha256=xxx, #integrity=xxx, #hash=xxx 格式
    const hashParams = new URLSearchParams(hash.slice(1))
    const integrityParams = ['sha256', 'integrity', 'hash']

    for (const param of integrityParams) {
      const value = hashParams.get(param)

      if (value && this.isValidIntegrity(value)) {
        return {
          value,
          source: 'hash',
          param,
          algorithm: this.detectAlgorithm(value),
          rawValue: value,
        }
      }
    }

    // 直接检查 hash 值本身（如 #sha256-abc123）
    const directHashMatch = hash.match(/(sha(?:256|384|512)-.+)/i)
    if (directHashMatch && this.isValidIntegrity(directHashMatch[1])) {
      return {
        value: directHashMatch[1],
        source: 'hash_direct',
        param: null,
        algorithm: this.detectAlgorithm(directHashMatch[1]),
        rawValue: directHashMatch[1],
      }
    }

    return null
  }

  /**
   * 在整个 URL 中搜索 integrity
   */
  private static extractFromFullUrl(url: string): IntegrityResult | null {
    const patterns = [
      /(sha(?:256|384|512)-[a-f0-9]{64,128})/i,
      /[?&](?:integrity|hash|sha256)=([a-f0-9]{64,128})/i,
      /[#&]sha(?:256|384|512)=([a-f0-9]{64,128})/i,
    ]

    for (const pattern of patterns) {
      const match = url.match(pattern)
      if (match) {
        const value = match[1] || match[0]
        if (this.isValidIntegrity(value)) {
          return {
            value,
            source: 'full_url',
            param: null,
            algorithm: this.detectAlgorithm(value),
            rawValue: value,
          }
        }
      }
    }

    return null
  }

  /**
   * 验证是否是有效的 integrity 值
   */
  private static isValidIntegrity(value: string): boolean {
    if (!value)
      return false

    // 1. 检查标准 integrity 格式：sha256-<hash>
    const standardPattern = /^sha(?:256|384|512)-[a-z0-9+/=]{44,88}$/i
    if (standardPattern.test(value))
      return true

    // 2. 检查纯哈希值（64字符十六进制）
    const hexPattern = /^[a-f0-9]{64}$/i
    if (hexPattern.test(value))
      return true

    // 3. 检查 Base64 哈希（44 字符，支持大小写）
    const base64Pattern = /^[a-z0-9+/=]{44}$/i
    if (base64Pattern.test(value))
      return true

    // 4. 检查 URL 安全的 Base64（base64url）
    const base64UrlPattern = /^[\w-]{43,86}$/
    return base64UrlPattern.test(value)
  }

  /**
   * 检测哈希算法类型
   */
  private static detectAlgorithm(value: string): string {
    if (value.startsWith('sha256-'))
      return 'sha256'
    if (value.startsWith('sha384-'))
      return 'sha384'
    if (value.startsWith('sha512-'))
      return 'sha512'
    if (/^[a-f0-9]{64}$/i.test(value))
      return 'sha256' // 假设64字符是sha256
    if (/^[a-z0-9+/=]{44}$/i.test(value))
      return 'sha256-base64' // Base64编码的sha256
    return 'unknown'
  }

  /**
   * 标准化URL（去除查询参数和哈希）
   */
  static normalizeUrl(url: string): string {
    try {
      const urlObj = new URL(url)
      return `${urlObj.origin}${urlObj.pathname}`
    }
    catch {
      // 如果URL解析失败，返回原始URL
      return url
    }
  }
}
